/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.rest.v1.draft.service;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.unidata.mdm.draft.context.DraftPublishContext;
import org.unidata.mdm.draft.context.DraftQueryContext;
import org.unidata.mdm.draft.context.DraftRemoveContext;
import org.unidata.mdm.draft.context.DraftUpsertContext;
import org.unidata.mdm.draft.dto.DraftPublishResult;
import org.unidata.mdm.draft.dto.DraftQueryResult;
import org.unidata.mdm.draft.dto.DraftRemoveResult;
import org.unidata.mdm.draft.dto.DraftUpsertResult;
import org.unidata.mdm.draft.service.DraftService;
import org.unidata.mdm.rest.system.service.AbstractRestService;
import org.unidata.mdm.rest.v1.draft.converter.DraftConverter;
import org.unidata.mdm.rest.v1.draft.ro.DraftProviderInfoRO;
import org.unidata.mdm.rest.v1.draft.ro.DraftQueryRO;
import org.unidata.mdm.rest.v1.draft.ro.GetDraftRequestRO;
import org.unidata.mdm.rest.v1.draft.ro.GetDraftResultRO;
import org.unidata.mdm.rest.v1.draft.ro.GetDraftTypesResultRO;
import org.unidata.mdm.rest.v1.draft.ro.PublishDraftRequestRO;
import org.unidata.mdm.rest.v1.draft.ro.PublishDraftResultRO;
import org.unidata.mdm.rest.v1.draft.ro.RemoveDraftRequestRO;
import org.unidata.mdm.rest.v1.draft.ro.RemoveDraftResultRO;
import org.unidata.mdm.rest.v1.draft.ro.UpsertDraftRequestRO;
import org.unidata.mdm.rest.v1.draft.ro.UpsertDraftResultRO;

import io.swagger.v3.oas.annotations.Operation;


@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Path("/")
public class DraftRestService extends AbstractRestService {

    private static final String SERVICE_TAG = "draft";

    @Autowired
    private DraftService draftService;

    @Autowired
    private DraftConverter draftConverter;

    @GET
    @Path("types")
    @Operation(
        description = "Returns list of draft provider types, currently registeres by the system.",
        method = HttpMethod.GET, tags = SERVICE_TAG)
    public GetDraftTypesResultRO types() {
        return new GetDraftTypesResultRO(draftService.providers().stream()
                .map(DraftProviderInfoRO::new)
                .collect(Collectors.toList()));
    }

    /**
     * Find drafts for request
     *
     * @return List of drafts
     */
    @POST
    @Path("drafts")
    @Operation(description = "Find drafts for request",
        method = HttpMethod.POST, tags = SERVICE_TAG)
    public GetDraftResultRO drafts(GetDraftRequestRO request) {

        DraftQueryRO query = request.getQuery();
        GetDraftResultRO result = new GetDraftResultRO();
        DraftQueryResult retval = draftService.drafts(DraftQueryContext.builder()
                .provider(Objects.isNull(query) ? null : query.getType())
                .subjectId(Objects.isNull(query) ? null : query.getSubjectId())
                .draftId(Objects.isNull(query) ? null : query.getDraftId())
                .parentDraftId(Objects.isNull(query) ? null : query.getParentDraftId())
                .owner(Objects.isNull(query) ? null : query.getOwner())
                .tags(Objects.isNull(query) ? null : query.getTags())
                .limit(request.getLimit())
                .start(request.getOffset())
                .build());

        result.setDrafts(retval.getDrafts().stream().map(draftConverter::toDraftRO).collect(Collectors.toList()));
        result.setTotalCount(selectCount(query));

        return result;
    }

    /**
     * Find drafts for request
     *
     * @return List of drafts
     */
    @POST
    @Path("upsert")
    @Operation(
        description = "Upsert draft, create or update display name if draft exists",
        method = HttpMethod.POST, tags = SERVICE_TAG)
    public UpsertDraftResultRO upsert(UpsertDraftRequestRO request) {
        Objects.requireNonNull(request, "Request can't be null");
        UpsertDraftResultRO result = new UpsertDraftResultRO();
        Map<String, String> parameters = ObjectUtils.defaultIfNull(request.getParameters(), Collections.emptyMap());
        DraftUpsertResult upsertResult = draftService.upsert(DraftUpsertContext.builder()
            .provider(request.getType())
            .subjectId(StringUtils.defaultString(request.getSubjectId(), StringUtils.EMPTY))
            .draftId(request.getDraftId())
            .parentDraftId(request.getParentDraftId())
            .owner(request.getOwner())
            .description(request.getDescription())
            .tags(request.getTags())
            .parameters(new HashMap<>(parameters))
            .build());
        if (upsertResult != null) {
            result.setDraft(draftConverter.toDraftRO(upsertResult.getDraft()));
        }
        return result;
    }

    /**
     * Find drafts for request
     *
     * @return result
     */
    @POST
    @Path("publish")
    @Operation(
        description = "Publish draft",
        method = HttpMethod.POST, tags = SERVICE_TAG)
    public PublishDraftResultRO publish(PublishDraftRequestRO request) {
        Objects.requireNonNull(request, "Request can't be null");
        PublishDraftResultRO result = new PublishDraftResultRO();
        DraftPublishResult publishResult = draftService.publish(DraftPublishContext.builder()
            .draftId(request.getDraftId())
            .force(request.isForce())
            .delete(request.isDelete())
            .build());
        if (publishResult != null) {
            result.setDraft(draftConverter.toDraftRO(publishResult.getDraft()));
        }
        return result;
    }

    /**
     * Remove draft for id
     *
     * @return result
     */
    @POST
    @Path("remove")
    @Operation(
        description = "Remove draft",
        method = HttpMethod.POST, tags = SERVICE_TAG)
    public RemoveDraftResultRO remove(RemoveDraftRequestRO request) {
        Objects.requireNonNull(request, "Request can't be null");
        RemoveDraftResultRO result = new RemoveDraftResultRO();
        DraftRemoveResult removeResult = draftService.remove(DraftRemoveContext.builder()
            .provider(request.getType())
            .draftId(request.getDraftId())
            .subjectId(request.getSubjectId())
            .owner(request.getOwner())
            .tags(request.getTags())
            .build());
        if (removeResult != null) {
            result.setRemoved(removeResult.getCount());
        }
        return result;
    }

    private long selectCount(DraftQueryRO query) {
        return draftService.count(DraftQueryContext.builder()
            .provider(Objects.isNull(query) ? null : query.getType())
            .subjectId(Objects.isNull(query) ? null : query.getSubjectId())
            .draftId(Objects.isNull(query) ? null : query.getDraftId())
            .parentDraftId(Objects.isNull(query) ? null : query.getParentDraftId())
            .owner(Objects.isNull(query) ? null : query.getOwner())
            .tags(Objects.isNull(query) ? null : query.getTags())
            .build());
    }
}
